//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <bsp.h>
#include <mmu.h>
#include <init.h>
#include <lcd.h>
#include "font.h"

const uint8_t c_u8DefaultColor  = 0xf;
const uint_t c_uTabStopValue    = 8;

EXTERN_C DECL_CODEINIT bool_t CDECL InitScreen();

EXTERN_C void PrintComPort(const char *szMessage);

STATIC bool_t s_bScreenInited = FALSE;
STATIC bool_t s_bComPortInited = FALSE;

STATIC void CDECL BspClearScreen();

STATIC void CDECL BspPrintScreen(const char *szMessage);

EXTERN_C DECL_CODEINIT bool_t CDECL InitConsole()
{
    s_bScreenInited = InitScreen();
    if (!s_bScreenInited) return FALSE;

    BspClearScreen();

    if (!s_bComPortInited) {
        s_bComPortInited = BspSetupComPort(COMPORT_PRINT, NULL);
    }

    return s_bComPortInited;
}

EXTERN_C void CDECL BspWriteConsole(const char *szMessage)
{
    if (NULL == szMessage) return;

    if (s_bScreenInited) BspPrintScreen(szMessage);

    if (!s_bComPortInited) {
        s_bComPortInited = BspSetupComPort(COMPORT_PRINT, NULL);
    }

    if (s_bComPortInited) PrintComPort(szMessage);
}

EXTERN_C void PrintComPort(const char *szMessage)
{
    assert(szMessage);

    for (const char *pc = szMessage; '\0' != *pc; pc++) {
        BspWriteComPort(COMPORT_PRINT, *pc);
    }
}

EXTERN_C DECL_CODEINIT bool_t CDECL InitScreen()
{
    // Set the palette to be a 1-to-1 mapping of the pixel vaules
    Outl(PALLSW, 0x76543210);
    Outl(PALMSW, 0xfedcba98);

    // Set video buffer size = (total bits in video buffer / 128) - 1
    const uint_t c_uVideoBufferSize =
        ((c_uLCDNumberOfPixels * c_uLCDBitsPerPixel) / 128) - 1;

    // Set line length = (number of pixels in line / 16) - 1
    const uint_t c_uLineLength = (c_uLCDPixelsPerRow / 16) - 1;

    const uint_t c_uLCDConfig =
        __GSMD | __GSEN | (13 << __AC_PRESCALE_SHIFT)
        | (__PIXEL_PRESCALE << __PIXEL_PRESCALE_SHIFT)
        | (c_uLineLength << __LINE_LENGTH_SHIFT) | c_uVideoBufferSize;

    Outl(LCDCON, c_uLCDConfig);

    // Set the start address for the LCD Frame Buffer
    Outb(FBADDR, c_paLCDFrameBufferStartAddr >> 28);

    SetVEE(120); // Set LCD 21V (arrange: 0 - 255)

    EnableLCD();

    SetLCDELEn();

    return TRUE;
}

STATIC uint_t s_uCursorLocation = 0;

STATIC void PrintChar(char ch, uint_t uLocation, uint8_t u8Color);

INLINE void SetCursorLocation(uint_t uCursorLocation)
{
    PrintChar('_', uCursorLocation, c_u8DefaultColor);
}

INLINE void ClearCursorLocation(uint_t uCursorLocation)
{
    PrintChar(' ', uCursorLocation, c_u8DefaultColor);
}

STATIC void CDECL BspClearScreen()
{
    register uint8_t *p = (uint8_t *)VIRTADDR(c_paLCDFrameBufferStartAddr);

    for (uint_t i = 0; i < c_uLCDFrameBufferSize; i++) {
        p[i] = 0;
    }

    s_uCursorLocation = 0;
    SetCursorLocation(s_uCursorLocation);
}

STATIC void PrintChar(char ch, uint_t uLocation, uint8_t u8Color)
{
    if (128 < ch) ch = ' ';

    uint_t uCharRow     = uLocation / c_uLCDCharsPerRow;
    uint_t uCharColumn  = uLocation % c_uLCDCharsPerRow;

    uint8_t *p =
        (uint8_t *)VIRTADDR(c_paLCDFrameBufferStartAddr)
        + uCharRow * c_uLCDBytesPerCharRow;

    const uint_t c_uBytesPerFontRow = c_uFontWidth * c_uLCDBitsPerPixel / 8;

    uint8_t fontRowBuf[c_uBytesPerFontRow];
    for (uint_t i = 0; i < c_uFontHeight; i++) {
        uint8_t u8FontRowData = c_u8FontData[(ch << 3) + i];

        for (uint_t j = 0; j < c_uBytesPerFontRow; j++, u8FontRowData <<= 1) {
            fontRowBuf[j] = (u8FontRowData & 0x80)? (u8Color & 0x0f) : 0;
            u8FontRowData <<= 1;
            fontRowBuf[j] |= (u8FontRowData & 0x80)? u8Color << 4 : 0;
        }

        memcpy(
            p + i * c_uLCDBytesPerRow + uCharColumn * c_uBytesPerFontRow,
            fontRowBuf, c_uBytesPerFontRow);
    }
}

STATIC void CDECL BspPrintScreen(const char *szMessage)
{
    assert(szMessage);

    ClearCursorLocation(s_uCursorLocation);

    for (const char *pc = szMessage; '\0' != *pc; pc++) {
        switch (*pc) {
            case '\a':
                BspBeep();
                break;

            case '\b':
                if (0 == s_uCursorLocation) break;

            s_uCursorLocation--;
            PrintChar(' ', s_uCursorLocation, c_u8DefaultColor);

            ClearCursorLocation(s_uCursorLocation);
            break;

            case '\n':
                s_uCursorLocation += c_uLCDCharsPerRow;
            // no break statement! Need translate LF to CRLF.

            case '\r':
                s_uCursorLocation -= s_uCursorLocation % c_uLCDCharsPerRow;
                break;

            case '\t':
                do {
                    PrintChar(' ', s_uCursorLocation, c_u8DefaultColor);
                    s_uCursorLocation++;
                } while (s_uCursorLocation % c_uTabStopValue);
                break;

            default:
                PrintChar(*pc, s_uCursorLocation, c_u8DefaultColor);
                s_uCursorLocation++;
        } // switch

        // Need Scroll ?
        if (s_uCursorLocation
            >= c_uLCDCharsPerRow * (c_uLCDCharsPerColumn - 1)) {
            uint8_t *p = (uint8_t *)VIRTADDR(c_paLCDFrameBufferStartAddr);
            memcpy(
                p, p + c_uLCDBytesPerCharRow,
                (c_uLCDCharsPerColumn - 1) * c_uLCDBytesPerCharRow);

            for (uint_t i = c_uLCDCharsPerRow * (c_uLCDCharsPerColumn - 1);
                i < c_uLCDCharsPerRow * c_uLCDCharsPerColumn; i++) {
                PrintChar(' ', i, c_u8DefaultColor);
            }

            s_uCursorLocation -= c_uLCDCharsPerRow;
        }
    }

    SetCursorLocation(s_uCursorLocation);
}
